package com.mathmu.fetion.protocol;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.HttpsURLConnection;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import com.mathmu.IncompetentPlatformException;

public class Account {
	
	static public final Pattern sipUriPattern 
					= Pattern.compile("sip:([0-9]+)(@fetion\\.com\\.cn.*)?");
	
	String sId;					// fetion number
	String userId;				// user id
	String mobileNumber;		// mobile phone number
	String password;			// raw password not hashed
	String sipURI;				// sipuri like 'sip:100@fetion.com.cn'
	
	String publicIp;			// public ip of current session
	String lastLoginIp;			// public ip of last login
	String lastLoginTime;		// last login time , got after sipc authentication

	String personalVersion;		// the version of personal information						
	String contactVersion;		// the version of contact information						
	String customConfigVersion;	// the version of custom config string,unused now			

	String configServersVersion;// the version of some related servers such as sipc server	
	String configHintsVersion;	// the version of hints										

	String nickname;			// nickname of yourself										
	String impression;			// mood phrase of yourself									
	String portraitCrc;			// a number generated by crc algorithm						
	String country;				// the country which your number belongs to					
	String province;			// the province which your number belongs to				
	String city;				// the city which your number belongs to 					
	int gender;					// the gender of your self									

	int state;					// presence state											
	int loginType;   			// using sid or mobileno									
	int loginStatus; 			// login status code 										
	long loginTimes;
	String ssic;				// cookie string read from reply message after ssi login 	
	String customConfig;		// custom config string used to set personal information	
	Captcha captcha;			// a struct used to generate picture code						 
	List<Contact> contactList;	// friend list of current user								
	List<Group> groupList;		// buddylist list of current user							
	Config config;				// config information										
	FetionSip sip;				// sip object used to handle sip event
	
	// TODO: is it right here to use Class.getName()?
	private static Logger logger 
		= Logger.getLogger(Account.class.getName());
	
	public void ssiAuthenticate() throws IOException, FetionProtocolException {
		final String passwordHash = Utility.hashPasswordV4(userId, 
															password);
		
		String noUri;
		if(loginType == 1) {
			noUri = String.format("mobileno=%s" , mobileNumber);
		} else {
			noUri = String.format("sid=%s" , sId);
		}
		
		String verifyUri = "";
		if(captcha != null && captcha.code != null)
		{
			verifyUri = String.format("&pid=%s&pic=%s&algorithm=%s", 
										captcha.guid, 
										captcha.code,
										captcha.algorithm);
		}
		final int passwordType = (userId.length() == 0 ? 1 : 2);
	
		final String wholeUrl = String.format("https://%s" +
						"/ssiportal/SSIAppSignInV4.aspx?" +
						"%s" +
					    "&" + "domains=fetion.com.cn" +
					    "%s" 
					    + "&" + "v4digest-type=%d" +
					    "&" + "v4digest=%s",
					    Utility.ssiDomain, noUri, verifyUri, passwordType, 
					    passwordHash);
		final HttpsURLConnection connection 
				= (HttpsURLConnection)(new URL(wholeUrl).openConnection());
		
		try {
			// TODO: fill in our own user-agent string here!
			connection.setRequestProperty("User-Agent", "IIC2.0/pc 3.3.0370");
			connection.connect();
			final int contentLength = connection.getContentLength();
			byte[] content = new byte[contentLength];
			InputStream in = connection.getInputStream();
			final int bytesRead = in.read(content, 0, contentLength);
			assert bytesRead == contentLength;
			
			parseSsiAuthenticationResponse(content);
		} finally {
			// TODO: we may try to reuse the connection in the future 
			connection.disconnect();
		}
	}
	
	private void parseSsiAuthenticationResponse(byte[] content) 
			throws FetionProtocolException {
		try {
			DocumentBuilder parser 
				= DocumentBuilderFactory.newInstance().newDocumentBuilder();
			Document doc = parser.parse(new ByteArrayInputStream(content));
			final Element root = Utility.getChildElement(doc, "results");
			// TODO: can we do this XML DOM traversal in a simpler way?
			loginStatus = Integer.parseInt(root.getAttribute("status-code"));
			if(loginStatus == 200)
			{
				logger.log(Level.INFO, "SSI login success");
				parseSsiAuthSuccess(root);
			}
			else
			{
				logger.log(Level.INFO, 
						"SSI login failed , status-code :%s" , loginStatus);
				parseSsiAuthFailed(root);
			}
		} catch (ParserConfigurationException e) {
			throw new IncompetentPlatformException(e);
		} catch (SAXException e) {
			throw new FetionProtocolException(e);
		} catch (IOException e) {
			throw new IncompetentPlatformException(e);
		}
	}
	
	private void parseSsiAuthSuccess(Node node) throws FetionProtocolException {
		final Element e = Utility.getChildElement(node, "user");

		sipURI = e.getAttribute("uri");
		
		final Matcher matcher = sipUriPattern.matcher(sipURI);
		if (!matcher.matches()) {
			throw new FetionProtocolException(
					String.format("malformed SIP URI \"%s\"", sipURI));
		}
		sId = matcher.group(1);
		
		// TODO: how about the user-status attribute?
		
		mobileNumber = e.getAttribute("mobile-no");
		
		userId = e.getAttribute("user-id");
		
		final Element credential
			= Utility.getChildElement(Utility.getChildElement(e, "credentials"),
										"credential");
		ssic = credential.getAttribute("c");
	}
	
	private void parseSsiAuthFailed(Node node) throws FetionProtocolException {
		// TODO: what's the element's tag name?
		final Node child = node.getFirstChild();
		if (child == null || child.getNodeType() != Node.ELEMENT_NODE) {
			throw new FetionProtocolException("expected child not found");
		}
		Element e = (Element)child;

		captcha = new Captcha();
		captcha.algorithm = e.getAttribute("algorithm");
		captcha.type = e.getAttribute("type");
		captcha.text = e.getAttribute("text");
		captcha.tips = e.getAttribute("tips");
	}
}
